package com.such.kit.mock.data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.such.kit.Callable;
import com.such.kit.Logger;
import com.such.kit.cache.CacheCommons;
import com.such.kit.datacast.CastCommons;
import com.such.kit.datastructure.tree.TreeCommons;
import com.such.kit.datastructure.tree.bean.Tree;
import com.such.kit.file.FileCommons;
import com.such.kit.file.bean.FileInfo;
import com.such.kit.file.support.FileFilter;
import com.such.kit.io.template.ExcelIOTemplate;
import com.such.kit.io.source.MemoryIOSource;
import com.such.kit.io.template.support.IOTemplate;
import com.such.kit.mock.data.bean.MockDataConfig;
import com.such.kit.mock.data.bean.MockDataRule;
import com.such.kit.placeholder.PlaceholderCommons;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;

public class MockDataFactory {

	private static String mockFileTypeXls = "xls";
	private static String mockFileTypeXlsx = "xlsx";
	private static String mockFileTypeJson = "json";
	private static String mockExcelTypeT = "T";
	private static String mockExcelTypeR = "R";
	private static String mockDataTree = ".tree";
	private static MockDataFactory factory = new MockDataFactory();
	private static String mockDataCache = CacheCommons.buildCacheName(MockDataFactory.class);

	private MockDataFactory() {

	}

	/**
	 * <p>方法名：getInstance</p>
	 * <p>描述：获取 MockDataFactory 实例</p>
	 * @return MockDataFactory
	 */
	public static MockDataFactory getInstance() {
		return factory;
	}

	/**
	 * <p>方法名：mocks</p>
	 * <p>描述：批量模拟文件类型数据</p>
	 * <p>
	 * 范围：<br>
	 * 1. excel：要求文件扩展名为 xls、xlsx，通过前缀 T、R 标识模板或数据，如 T.xxx.xls、R.xxx.xls，系统通过 T.[xxx.xls] 部分是否相同进行配对<br>
	 * 2. json：要求文件扩展名为 json，如 xxx.json
	 * </p>
	 * @param mockDir 模拟文件夹路径
	 */
	public void mocks(String mockDir) {
		List<FileInfo> fileInfos = FileCommons.listFolder(mockDir, new FileFilter() {
			@Override
			public boolean accept(FileInfo fileInfo) {
				String type = fileInfo.getType();
				return mockFileTypeXls.equals(type) || mockFileTypeXlsx.equals(type) || mockFileTypeJson.equals(type);
			}
		});
		if (ValidateCommons.isNotEmpty(fileInfos)) {
			Map<String, String> templateMap = new HashMap<String, String>();
			Map<String, String> resourceMap = new HashMap<String, String>();
			for (FileInfo fileInfo : fileInfos) {
				String name = fileInfo.getName();
				String type = fileInfo.getType();
				String path = fileInfo.getPath();

				if (mockFileTypeXls.equals(type) || mockFileTypeXlsx.equals(type)) {
					String excelType = name.substring(0, 1);
					name = name.substring(2);
					if (mockExcelTypeT.equals(excelType)) {
						templateMap.put(name, path);
					} else if (mockExcelTypeR.equals(excelType)) {
						resourceMap.put(name, path);
					}
				} else if (mockFileTypeJson.equals(type)) {
					mock(path);
				}
			}
			for (String key : templateMap.keySet()) {
				mock(templateMap.get(key), resourceMap.get(key));
			}
		}
	}

	/**
	 * <p>方法名：mock</p>
	 * <p>描述：通过 json 文件模拟数据</p>
	 * <p>
	 * 格式：<br>
	 * 	{<br>
	 * 	&emsp;"mockKey1": [{"field1": "value1", "field2": "value2", "field3": "value3" ... }], <br>
	 * 	&emsp;"mockKey2": [{"field1": "value1", "field2": "value2", "field3": "value3" ... }]<br>
	 * 	}
	 * </p>
	 * @param json 文件路径
	 */
	public void mock(String json) {
		try {
			Map<Object, Object> store = JSON.parseObject(json, new TypeReference<Map<Object, Object>>() {});
			CacheCommons.put(mockDataCache, store);
		} catch (Exception e) {
			Logger.error(getClass(), "Mock 数据异常", e);
		}
	}

	/**
	 * <p>方法名：mock</p>
	 * <p>描述：通过 ExcelIO 模拟数据</p>
	 * @param template excel 模板，支持 xls、xlsx 格式，缓存键通过模板中的 IO_INPUT_EXPRESSION 定义
	 * @param resource excel 数据，支持 xls、xlsx 格式
	 */
	public void mock(String template, String resource) {
		if (ValidateCommons.isNotEmpty(template) && ValidateCommons.isNotEmpty(resource)) {
			try {
				MemoryIOSource ioSource = new MemoryIOSource();
				IOTemplate io = new ExcelIOTemplate(template);
				io.input(ioSource, resource);
				for (String key : ioSource.getInputKeys()) {
					CacheCommons.put(mockDataCache, key, ioSource.getInputResult(key));
				}
			} catch (Exception e) {
				Logger.error(getClass(), "Mock 数据异常", e);
			}
		}
	}

	/**
	 * <p>方法名：mock</p>
	 * <p>描述：通过 MockDataConfig 模拟数据</p>
	 * @param key 缓存键
	 * @param total 生成数量
	 * @param configs mock 配置
	 */
	public void mock(String key, int total, MockDataConfig... configs) {
		if (ValidateCommons.isNotEmpty(key) && ValidateCommons.isNotEmpty(configs)) {
			Random random = new Random();
			List<Object> results = new ArrayList<Object>();
			for (int i = 1; i <= total; i++) {
				Map<String, String> result = new HashMap<String, String>();
				for (MockDataConfig config : configs) {
					String field = config.getField();
					String value = config.getExpression();
					String expression = config.getExpression();
					MockDataRule[] rules = config.getRules();
					if (ValidateCommons.isNotEmpty(rules)) {
						Map<String, String> params = new HashMap<String, String>();
						for (MockDataRule rule : rules) {
							String ruleKey = rule.getRuleKey();
							String ruleType = rule.getRuleType();
							int length = rule.getLength();
							boolean padable = rule.padable();
							String padMode = rule.getPadMode();
							String[] valueScopes = rule.getValueScopes();

							String ruleValue = "";
							if (MockDataRule.RULE_TYPE_SEQUENCE_KEY.equals(ruleType)) {
								if (padable) {
									if (MockDataRule.PAD_MODE_LEFT.equals(padMode)) {
										ruleValue = CastCommons.numberToString(i, length);
									} else if (MockDataRule.PAD_MODE_RIGHT.equals(padMode)) {
										ruleValue = CastCommons.numberToString(i);
										ruleValue = StringCommons.merger(ruleValue, StringCommons.repeat("0", length - ruleValue.length()));
									}
								} else {
									ruleValue = CastCommons.numberToString(i);
								}
							} else if (MockDataRule.RULE_TYPE_NUMBER_KEY.equals(ruleType)) {
								int max = CastCommons.stringToInteger(StringCommons.repeat("9", length));
								int randomValue = random.nextInt(max) + 1;
								if (padable) {
									if (MockDataRule.PAD_MODE_LEFT.equals(padMode)) {
										ruleValue = CastCommons.numberToString(randomValue, length);
									} else if (MockDataRule.PAD_MODE_RIGHT.equals(padMode)) {
										ruleValue = CastCommons.numberToString(randomValue);
										ruleValue = StringCommons.merger(ruleValue, StringCommons.repeat("0", length - ruleValue.length()));
									}
								} else {
									ruleValue = CastCommons.numberToString(randomValue);
								}
							} else if (MockDataRule.RULE_TYPE_RANDOM_KEY.equals(ruleType) || MockDataRule.RULE_TYPE_SCOPE_KEY.equals(ruleType)) {
								int len = valueScopes.length;
								StringBuffer valueScope = new StringBuffer();
								for (int j = 0; j < length; j++) {
									int index = random.nextInt(len);
									valueScope.append(valueScopes[index]);
								}
								ruleValue = valueScope.toString();
							}
							params.put(ruleKey, ruleValue);
						}
						value = PlaceholderCommons.replacePlaceholder(expression, params);
					}
					result.put(field, value);
				}
				results.add(result);
			}
			CacheCommons.put(mockDataCache, key, results);
		}
	}

	/**
	 * <p>方法名：buildTrees</p>
	 * <p>描述：将模拟数据构建为树形结构</p>
	 * <p>注意：构建树的数据要求数据顺序从根节点往叶子节点排序</p>
	 * @param key 模拟键
	 * @param caller 回调函数，返回字符串数组，数组长度为 3，依次存放 [id, parentId, title]
	 * @throws Exception 
	 */
	public void buildTree(String key, Callable<Map<String, Object>, String[]> caller) throws Exception {
		List<Map<String, Object>> results = getResults(key);
		Tree<Map<String, Object>> tree = new Tree<Map<String, Object>>();
		tree.add(TreeCommons.createNodes(results, caller));
		CacheCommons.put(mockDataCache, getTreeKey(key), tree);
	}

	/**
	 * <p>方法名：getResults</p>
	 * <p>描述：获取模拟数据</p>
	 * @param key 模拟键
	 * @return 模拟数据
	 */
	public <T> T getResults(String key) {
		return CacheCommons.get(mockDataCache, key);
	}

	/**
	 * <p>方法名：getTrees</p>
	 * <p>描述：获取树形模拟数据</p>
	 * @param key 模拟键
	 * @return 树形模拟数据
	 */
	public Tree<Map<String, Object>> getTree(String key) {
		return CacheCommons.get(mockDataCache, getTreeKey(key));
	}

	private String getTreeKey(String key) {
		return StringCommons.merger(key, mockDataTree);
	}

}
